//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <aura.h>
#include "win32.h"
#include "private.h"

#define BUFSIZE 10*1024

//static void DisconncetPipe(HANDLE hPipe)
//{
//    FlushFileBuffers(hPipe);
//    DisconnectNamedPipe(hPipe);
//  CloseHandle(hPipe);
//}

static BOOL s_bDidConnection = FALSE;

Aura_ECode DestroyElAuraAsDuty();

static
Aura_ECode StartElAuraAsNeed(Aura_PWChar name)
{
    if (wcscmp((wchar_t *)name, L"ElCentral")) {
        return AURA_NOERROR;
    }

    BOOL   res = FALSE;
    HANDLE hEvent = NULL;
    PROCESS_INFORMATION processInfo;
    STARTUPINFOW startupInfo;
    wchar_t commandLine[AURA_MAX_PATH] = L"";
    wcscat(commandLine, (wchar_t *)L"elaura.exe");

    hEvent = CreateEventW(NULL, FALSE, FALSE,
                        L"ElAuraStartupNotifyEvent");
    if (NULL == hEvent) {
        goto E_FAIL_EXIT;
    }

    memset((void *)&processInfo, 0, sizeof(PROCESS_INFORMATION));
    memset((void *)&startupInfo, 0, sizeof(STARTUPINFOW));
    startupInfo.cb = sizeof(STARTUPINFOW);

    res = CreateProcessW(NULL, commandLine,
                      NULL, NULL,
                      FALSE,
                      0,
                      NULL, NULL,
                      &startupInfo,
                      &processInfo);
    if (!res) {
        goto E_FAIL_EXIT;
    }

    CloseHandle(processInfo.hThread);
    CloseHandle(processInfo.hProcess);
    s_bDidConnection = TRUE;

    res = WaitForSingleObjectEx(hEvent, (DWORD)INFINITE, FALSE);
    if (res != WAIT_OBJECT_0) {
        DestroyElAuraAsDuty();
        goto E_FAIL_EXIT;
    }
    CloseHandle(hEvent);

    return AURA_NOERROR;

E_FAIL_EXIT:
    if (NULL != hEvent) {
        CloseHandle(hEvent);
    }
    return GetLastECodeError();
}

Aura_ECode DestroyElAuraAsDuty()
{
    HANDLE hEvent = NULL;

    if (!s_bDidConnection) {
        return AURA_NOERROR;
    }

    hEvent = CreateEventW(NULL,
                     FALSE,
                     FALSE,
                     L"ElAuraNotifyToExitEvent");
    if ((NULL == hEvent)
        || (ERROR_ALREADY_EXISTS != GetLastError())){
        goto E_FAIL_EXIT;
    }

    SetEvent(hEvent);
    CloseHandle(hEvent);

    return AURA_NOERROR;

E_FAIL_EXIT:
    return GetLastECodeError();
}

// Try to open a named pipe; wait for it, if necessary.
static HANDLE getClientPipeInstance(Aura_PWChar clientPipeName)
{
    HANDLE hPipe = INVALID_HANDLE_VALUE;
    wchar_t name[AURA_MAX_PATH];

    swprintf(name,  L"\\\\.\\pipe\\%s", (const wchar_t*)clientPipeName);
    while (1) {
        hPipe = CreateFileW(
                (WCHAR*)name,     // pipe name
                GENERIC_READ |      // read and write access
                GENERIC_WRITE,
                0,                    // no sharing
                NULL,                 // default security attributes
                OPEN_EXISTING,        // opens existing pipe
                FILE_FLAG_OVERLAPPED, // default attributes
                NULL);                // no template file

        if (hPipe != INVALID_HANDLE_VALUE)
            return hPipe;

        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
            //maybe server hasn't create pipe yet,
            //so retry in serveral times
            static int iRetryTimes = 0;
            if (iRetryTimes++ < 5) {
                Sleep(50);
                continue;
            }
        }

        // Exit if an error other than ERROR_PIPE_BUSY occurs.
        if (GetLastError() != ERROR_PIPE_BUSY) {
            return INVALID_HANDLE_VALUE;
        }

        // All pipe instances are busy, so wait for 20 seconds.
        if (!WaitNamedPipeW((WCHAR*)name, 20000)) {
            return INVALID_HANDLE_VALUE;
        }
    }
}

AURA_API AuraIpcChannel_Create(
        /* [in] */ Aura_PWChar name,
        /* [out] */ Aura_ChannelId *pChannelId)
{
    WCHAR fullName[AURA_MAX_PATH];
    HANDLE hPipe = INVALID_HANDLE_VALUE;

    assert(AURA_NULL != name);
    assert(L'\0' != (wchar_t)name[0]);
    assert(AURA_NULL != pChannelId);

    swprintf((wchar_t *)fullName, L"\\\\.\\pipe\\%s", (const wchar_t *)name);
    hPipe = CreateNamedPipeW(
            fullName,                 // pipe name
            PIPE_ACCESS_DUPLEX |      // read/write access
            FILE_FLAG_OVERLAPPED,     // overlapped mode
            PIPE_TYPE_MESSAGE |       // message type pipe
            PIPE_READMODE_MESSAGE |   // message-read mode
            PIPE_WAIT,                // blocking mode
            PIPE_UNLIMITED_INSTANCES, // max. instances
            BUFSIZE,                  // output buffer size
            BUFSIZE,                  // input buffer size
            NMPWAIT_USE_DEFAULT_WAIT, // client time-out
            NULL);                    // default security attribute
    if (INVALID_HANDLE_VALUE == hPipe) {
        goto ErrorExit;
    }

    *pChannelId = (Aura_ChannelId)hPipe;
    return AURA_NOERROR;

ErrorExit:
    return GetLastECodeError();
}

AURA_API AuraIpcChannel_Destroy(
        /* [in] */ Aura_ChannelId channelId)
{
    HANDLE hPipe = (HANDLE)channelId;

    assert(NULL != hPipe);
    assert(INVALID_HANDLE_VALUE != hPipe);
    CloseHandle(hPipe);

    return AURA_NOERROR;
}

AURA_API AuraIpcChannel_WaitForConnection(
        /* [in] */ Aura_PWChar channelName,
        /* [out] */ Aura_ChannelId * pListenedId,
        /* [out] */ Aura_ChannelId * pChannelId)
{
    BOOL fConnected;
    assert(NULL != pListenedId);
    Aura_ChannelId channelId = *pListenedId;
    wchar_t serverPipeName[AURA_MAX_PATH];
    HANDLE hListenPipe = INVALID_HANDLE_VALUE;

    assert(AURA_NULL != channelName);
    assert(L'\0' != (wchar_t)channelName[0]);
    assert(NULL != pChannelId);
    assert(INVALID_HANDLE_VALUE != (HANDLE)channelId);
    assert(NULL != (HANDLE)channelId);

    swprintf(serverPipeName,  L"\\\\.\\pipe\\%s", (const wchar_t*)channelName);

    fConnected = ConnectNamedPipe((HANDLE)channelId, NULL)
                ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
    if (!fConnected) {
        goto Aura_Exit;
    }

    hListenPipe = CreateNamedPipeW(
            (WCHAR*)serverPipeName,           // pipe name
            PIPE_ACCESS_DUPLEX |      // read/write access
            FILE_FLAG_OVERLAPPED,     // overlapped mode
            PIPE_TYPE_MESSAGE |       // message type pipe
            PIPE_READMODE_MESSAGE |   // message-read mode
            PIPE_WAIT,                // blocking mode
            PIPE_UNLIMITED_INSTANCES, // max. instances
            BUFSIZE,                  // output buffer size
            BUFSIZE,                  // input buffer size
            NMPWAIT_USE_DEFAULT_WAIT, // client time-out
            NULL);                    // default security attribute

    if (INVALID_HANDLE_VALUE == hListenPipe) {
        goto Aura_Exit;
    }

    *pChannelId = channelId;
    *pListenedId = (Aura_ChannelId)hListenPipe;

    return AURA_NOERROR;

Aura_Exit:
    return GetLastECodeError();
}

AURA_API AuraIpcChannel_Read(
        /* [in] */ Aura_ChannelId channelId,
        /* [in] */ Aura_PVoid pBuffer,
        /* [in] */ Aura_UInt32 bufferSize)
{
    BOOL fSuccess = FALSE;
    HANDLE hPipe = (HANDLE)channelId;
    DWORD cbBytesRead, dwWait;
    Aura_ECode ec = AURA_NOERROR;

    assert(NULL != hPipe);
    assert(INVALID_HANDLE_VALUE != hPipe);

    OVERLAPPED oConnect;
    memset(&oConnect, 0, sizeof(oConnect));
    HANDLE hConnectEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
    if (hConnectEvent == NULL) {
        Aura_printf("CreateEvent failed with %d.\n", GetLastError());
        ec = GetLastECodeError();
        goto Aura_Exit;
    }
    oConnect.hEvent = hConnectEvent;

    fSuccess = ReadFile(
            hPipe,          // handle to pipe
            pBuffer,        // buffer to receive data
            bufferSize,     // size of buffer
            &cbBytesRead,   // number of bytes read
            &oConnect);     // overlapped I/O
    if (!fSuccess && GetLastError() == ERROR_IO_PENDING) {
        while (1) {
            dwWait = WaitForSingleObjectEx(
                 hConnectEvent, (DWORD)INFINITE, TRUE);
            if (dwWait == WAIT_IO_COMPLETION) {
                ec = AURA_E_INTERRUPTED;
                CloseHandle(hPipe);
                break;
            }
            else if (dwWait == WAIT_OBJECT_0) {
                fSuccess = GetOverlappedResult(hPipe, &oConnect, &cbBytesRead, TRUE);
                if (!fSuccess) {
                    ec = GetLastECodeError();
                }
                break;
            }
        }
    }
    else if (!fSuccess || cbBytesRead == 0) {
        ec = GetLastECodeError();
    }

Aura_Exit:
    if (hConnectEvent) {
        CloseHandle(hConnectEvent);
    }
    return ec;
}

AURA_API AuraIpcChannel_AsyncWrite(
        /* [in] */ Aura_ChannelId channelId,
        /* [in] */ Aura_PVoid pBuffer,
        /* [in] */ Aura_UInt32 bufferSize)
{
    BOOL fSuccess = FALSE;
    HANDLE hPipe = (HANDLE)channelId;
    DWORD cbWritten = 0, dwWait;
    Aura_ECode ec = AURA_NOERROR;

    assert(NULL != hPipe);
    assert(INVALID_HANDLE_VALUE != hPipe);

    OVERLAPPED oConnect;
    memset(&oConnect, 0, sizeof(oConnect));
    HANDLE hConnectEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
    if (hConnectEvent == NULL) {
        Aura_printf("CreateEvent failed with %d.\n", GetLastError());
        ec = GetLastECodeError();
        goto Aura_Exit;
    }
    oConnect.hEvent = hConnectEvent;

    fSuccess = WriteFile(
            hPipe,          // handle to pipe
            pBuffer,        // buffer to write from
            bufferSize,     // number of bytes to write
            &cbWritten,     // number of bytes written
            &oConnect);     // overlapped I/O
    if (!fSuccess && GetLastError() == ERROR_IO_PENDING) {
        while (1) {
            dwWait = WaitForSingleObjectEx(
                 hConnectEvent, (DWORD)INFINITE, TRUE);
            if (dwWait == WAIT_IO_COMPLETION) {
                ec = AURA_E_INTERRUPTED;
                CloseHandle(hPipe);
                break;
            }
            else if (dwWait == WAIT_OBJECT_0) {
                fSuccess = GetOverlappedResult(hPipe, &oConnect, &cbWritten, TRUE);
                if (!fSuccess) {
                    ec = GetLastECodeError();
                }
                break;
            }
        }
    }
    else if (!fSuccess || bufferSize != cbWritten) {
      //  DisconncetPipe(hPipe);
        ec = GetLastECodeError();
    }

Aura_Exit:
    if (hConnectEvent) {
        CloseHandle(hConnectEvent);
    }
    return ec;
}

AURA_API AuraIpcChannel_SyncWrite(
        /* [in] */ Aura_ChannelId channelId,
        /* [in] */ Aura_PVoid pInBuffer,
        /* [in] */ Aura_UInt32 inBufferSize,
        /* [out] */ Aura_PVoid pOutBuffer,
        /* [in] */ Aura_UInt32 outBuferSize)
{
    HANDLE hPipe  = (HANDLE)channelId;;
    BOOL fSuccess = FALSE;
    DWORD cbWritten, cbRead, dwWait;

    assert(NULL != hPipe);
    assert(INVALID_HANDLE_VALUE != hPipe);
    assert(NULL != pOutBuffer);

    OVERLAPPED oConnect;
    memset(&oConnect, 0, sizeof(oConnect));
    HANDLE hConnectEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
    if (hConnectEvent == NULL) {
        Aura_printf("CreateEvent failed with %d.\n", GetLastError());
        goto E_FAIL_EXIT;
    }
    oConnect.hEvent = hConnectEvent;

    // Send the request to the server.
    fSuccess = WriteFile(
            hPipe,             // pipe handle
            pInBuffer,         // message
            inBufferSize,      // message length
            &cbWritten,        // bytes written
            &oConnect);        // overlapped
    if (!fSuccess && GetLastError() == ERROR_IO_PENDING) {
        while (1) {
            dwWait = WaitForSingleObjectEx(
                 hConnectEvent, (DWORD)INFINITE, TRUE);
            if (dwWait == WAIT_IO_COMPLETION) {
                CloseHandle(hConnectEvent);
                CloseHandle(hPipe);
                return AURA_E_INTERRUPTED;
            }
            else if (dwWait == WAIT_OBJECT_0) {
                fSuccess = GetOverlappedResult(hPipe, &oConnect, &cbWritten, TRUE);
                if (!fSuccess) {
                    goto E_FAIL_EXIT;
                }
                break;
            }
        }
    }
    else if (!fSuccess) {
        goto E_FAIL_EXIT;
    }

    // Read the reply from the server.
    fSuccess = ReadFile(
            hPipe,              // pipe handle
            pOutBuffer,         // buffer to receive reply
            outBuferSize,       // size of buffer
            &cbRead,            // number of bytes read
            &oConnect);         // overlapped

    //   if (!fSuccess && GetLastError() != ERROR_MORE_DATA)
    //       return -1;
    if (!fSuccess && GetLastError() == ERROR_IO_PENDING) {
        while (1) {
            dwWait = WaitForSingleObjectEx(
                 hConnectEvent, (DWORD)INFINITE, TRUE);
            if (dwWait == WAIT_IO_COMPLETION) {
                CloseHandle(hConnectEvent);
                CloseHandle(hPipe);
                return AURA_E_INTERRUPTED;
            }
            else if (dwWait == WAIT_OBJECT_0) {
                fSuccess = GetOverlappedResult(hPipe, &oConnect, &cbRead, TRUE);
                if (!fSuccess) {
                    goto E_FAIL_EXIT;
                }
                break;
            }
        }
    }
    else if (!fSuccess) {
        goto E_FAIL_EXIT;
    }
    if (hConnectEvent) {
        CloseHandle(hConnectEvent);
    }
    return AURA_NOERROR;

E_FAIL_EXIT:
    if (hConnectEvent) {
        CloseHandle(hConnectEvent);
    }
    return GetLastECodeError();
}


AURA_API AuraIpcChannel_Connect(
        /* [in] */ Aura_PWChar channelName,
        /* [out] */ Aura_ChannelId * pChannelId)
{
    HANDLE hPipe = NULL;
    DWORD dwMode = 0;
    BOOL fSuccess = FALSE;

    static LONG s_bConnectCount = 0;
    assert(NULL != channelName);
    assert(L'\0' != (wchar_t)channelName[0]);
    assert(NULL != pChannelId);

    do {
        Aura_ECode ec = AURA_NOERROR;

        hPipe = getClientPipeInstance(channelName);
        if (INVALID_HANDLE_VALUE != hPipe) {
            s_bConnectCount++;
            break;
        }
        if (s_bConnectCount > 0) {
            break;
        }

        ec = StartElAuraAsNeed(channelName);
        if (AURA_FAILED(ec)) {
            break;
        }
    }while(!s_bConnectCount);

    if (INVALID_HANDLE_VALUE == hPipe) {
        goto E_FAIL_EXIT;
    }

    // The pipe connected; change to message-read mode.
    dwMode = PIPE_READMODE_MESSAGE;
    fSuccess = SetNamedPipeHandleState(
            hPipe,    // pipe handle
            &dwMode,  // new pipe mode
            NULL,     // don't set maximum bytes
            NULL);    // don't set maximum time

    if (!fSuccess) {
        CloseHandle(hPipe);
        goto E_FAIL_EXIT;
    }

    *pChannelId = (Aura_ChannelId)hPipe;
    return AURA_NOERROR;

E_FAIL_EXIT:
    if (NULL != hPipe) {
        CloseHandle(hPipe);
    }
    return GetLastECodeError();
}

AURA_API AuraIpcChannel_Disconnect(
        /* [in] */ Aura_ChannelId channelId)
{
    HANDLE hPipe = (HANDLE)channelId;
    assert(NULL != hPipe);
    assert(INVALID_HANDLE_VALUE != hPipe);

    DisconnectNamedPipe(hPipe);
    CloseHandle(hPipe);

    return AURA_NOERROR;
}

